package ch.quantasy.android.speech;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.speech.tts.TextToSpeech;
import android.speech.tts.TextToSpeech.OnInitListener;
import android.speech.tts.TextToSpeech.OnUtteranceCompletedListener;
import android.util.Log;
import ch.quantasy.android.speech.string.LocalizedString;

public class SpeechService extends Service implements
		OnUtteranceCompletedListener {
	private TextToSpeech tts;
	private Lock babelLock;
	private Condition babelLockCondition;
	private List<LocalizedString> phrases;
	private HashMap<String, String> utteranceOptions;
	private String MESSAGE_COMPLETE;
	private boolean isBabeling;
	private boolean stop;
	private Thread babelThread;

	public static Intent getSpeechIntent(LocalizedString... localizedStrings) {

		Intent speechIntent = new Intent(
				"ch.quantasy.android.speech.SPEECH_SERVICE");
		if (localizedStrings != null && localizedStrings.length > 0)
			for (LocalizedString localizedString : localizedStrings) {
				speechIntent.putExtra("Phrase", localizedString);
			}
		return speechIntent;
	}

	public SpeechService() {
	}

	public SpeechService getService() {
		return SpeechService.this;
	}

	@Override
	public IBinder onBind(Intent intent) {
		return null;
	}

	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		LocalizedString phrase = (LocalizedString) intent.getExtras().get(
				"Phrase");
		if (phrase != null)
			addPhrase(phrase);
		return super.onStartCommand(intent, flags, startId);
	}

	@Override
	public void onCreate() {
		super.onCreate();

		babelLock = new ReentrantLock();
		babelLockCondition = babelLock.newCondition();
		this.phrases = new ArrayList<LocalizedString>();
		tts = new TextToSpeech(this.getApplicationContext(), onInitHandler);
	}

	@Override
	public void onDestroy() {
		// TODO Auto-generated method stub
		stop = true;
		babelThread.interrupt();
		super.onDestroy();

	}

	private final OnInitListener onInitHandler = new OnInitListener() {
		public void onInit(int arg0) {
			if (TextToSpeech.SUCCESS == arg0) {
				initSpeechEngineOptions();
				babelThread = new Thread(new BabelRunner());
				babelThread.start();
			}
		};
	};

	private void initSpeechEngineOptions() {
		if (TextToSpeech.LANG_MISSING_DATA == tts
				.isLanguageAvailable(Locale.UK)) {
			Intent installIntent = new Intent();
			installIntent
					.setAction(TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA);
			installIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
			startActivity(installIntent);
		}
		this.MESSAGE_COMPLETE = "end of message";
		this.utteranceOptions = new HashMap<String, String>();
		// We are interested in 'Message-Complete'
		this.utteranceOptions.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID,
				this.MESSAGE_COMPLETE);
		// We attach a listener (ourself) to the tts for 'Message-Complete'
		this.tts.setOnUtteranceCompletedListener(this);
		// Give it some speed
		this.tts.setSpeechRate(1.2f);
	}

	/**
	 * @param phrase
	 *            a localizedString or null if you want to end the application
	 */
	public synchronized void addPhrase(final LocalizedString phrase) {
		try {
			this.babelLock.lock();
			this.phrases.add(phrase);
			this.babelLockCondition.signalAll();
		} finally {
			this.babelLock.unlock();
		}
	}

	// Every time called, it will remove a line and loads the tts with the
	// data. (This method returns immediately.
	private void babel(LocalizedString phrase) {
		Log.i(SpeechService.class.getName(), "speaking: "+phrase);
		if (phrase == null)
			return;
		try {
			babelLock.lock();
			this.isBabeling = true;
			// We tell the tts which language to use for pronounciation
			this.tts.setLanguage(phrase.locale);
			// We tell the tts the sentence which it shall speak
			this.tts.speak(phrase.string, TextToSpeech.QUEUE_FLUSH,
					this.utteranceOptions);
		} finally {
			babelLock.unlock();
		}
	}

	class BabelRunner implements Runnable {
		public void run() {
			try {
				while (!stop) {
					try {
						babelLock.lock();
						while (isBabeling || phrases.isEmpty()) {
							babelLockCondition.await();
						}
						babel(phrases.remove(0));
					} finally {
						babelLock.unlock();
					}
				}
			} catch (InterruptedException e) {
			}
		}
	}

	public synchronized void onUtteranceCompleted(final String uttId) {
		if (uttId.equals(this.MESSAGE_COMPLETE)) {
			// If the tts has finished speaking, the next sentence shall be
			// spoken...
			try {
				babelLock.lock();
				this.isBabeling = false;
				babelLockCondition.signalAll();
			} finally {
				babelLock.unlock();
			}
		}
	}

}
